﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Ordinaire.Db;
using Ordinaire.Security.Data;

namespace Ordinaire.Security
{
    /// <summary>
    /// Manages storage of membership information in a SQL Server database.
    /// </summary>
    public class MembershipManager
    {
        #region Variables

        /// <summary>
        /// Instance of Ordinaire.Security.UserDataManager.
        /// </summary>
        private UserDataManager dataManager;

        #endregion

        #region Constructor

        /// <summary>
        /// Returns new instance of Ordinaire.Security.MembershipManager.
        /// </summary>
        public MembershipManager()
        {
            dataManager = new UserDataManager();
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Processes a request to update the password for a membership user.
        /// </summary>
        /// <param name="username">the user to update the password for</param>
        /// <param name="oldPassword">the current password for the specified user</param>
        /// <param name="newPassword">the new password for the specified user</param>
        /// <returns>true if the password was updated successfully; otherwise, false</returns>
        public bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            if (String.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            if (String.IsNullOrEmpty(oldPassword))
            {
                throw new ArgumentNullException("oldPassword");
            }

            if (String.IsNullOrEmpty(newPassword))
            {
                throw new ArgumentNullException("newPassword");
            }

            try
            {
                string hashPassword = Cryptographer.Hash(oldPassword);

                OContext context = dataManager.Context;
                Users    data    = context.Users.Where(Users.AreValid(username, hashPassword)).SingleOrDefault();

                // Old password provided is invalid
                if (data == null)
                {
                    return false;
                }

                // Update with new password
                data.Password  = Cryptographer.Hash(newPassword);
                data.UpdatedBy = username;
                context.SubmitChanges();
                return true;
            }
            catch (Exception ex)
            {
                throw ExceptionFactory.Create<OException>(ex, ExceptionMessage.Failure, "change password");
            }
        }

        /// <summary>
        /// Adds a new membership user to the data source.
        /// </summary>
        /// <param name="username">the user name for the new user</param>
        /// <param name="password">the password for the new user</param>
        public void CreateUser(string username, string password)
        {
            if (String.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }
            if (String.IsNullOrEmpty(password))
            {
                throw new ArgumentNullException("password");
            }

            Users data = dataManager.Insert(username, password);
        }

        /// <summary>
        /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <param name="username">the name of the user to get information for</param>
        /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
        /// <returns>a Ordinaire.Security.User object populated with the specified user's information from the data source</returns>
        public User GetUser(string username, bool userIsOnline = false)
        {
            if (String.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            try
            {
                OContext context = dataManager.Context;
                Users    data    = context.Users.Where(Users.UsernameIs(username)).SingleOrDefault();
                if (data == null)
                {
                    return null;
                }

                User user = data.ToUser(typeof(MembershipManager).FullName);

                // Update user's last activity date
                if (userIsOnline)
                {
                    data.ActivityDate = DateTime.Now;
                    context.SubmitChanges();
                }

                return user;
            }
            catch (Exception ex)
            {
                throw ExceptionFactory.Create<OException>(ex, ExceptionMessage.Failure, "get user");
            }
        }

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <param name="username">the name of the user to validate</param>
        /// <param name="password">the password for the specified user</param>
        /// <returns>true if specified username and password are valid; otherwise false</returns>
        public bool ValidateUser(string username, string password)
        {
            if (String.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            if (String.IsNullOrEmpty(password))
            {
                throw new ArgumentNullException("password");
            }

            try
            {
                OContext context       = dataManager.Context;
                string   hashPassword  = Cryptographer.Hash(password);
                Users    data          = context.Users.Where(Users.AreValid(username, hashPassword)).SingleOrDefault();

                // both username and password are not valid
                if (data == null)
                {
                    return false;
                }

                // Update last login date
                data.LoginDate = DateTime.Now;
                context.SubmitChanges();
                return true;
            }
            catch (Exception ex)
            {
                throw ExceptionFactory.Create<OException>(ex, ExceptionMessage.Failure, "validate user");
            }
        }

        #endregion
    }
}
